package de.lighti.starcraft;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.Kernel32Util;
import com.sun.jna.platform.win32.Tlhelp32;
import com.sun.jna.platform.win32.WinBase.PROCESS_INFORMATION;
import com.sun.jna.platform.win32.WinBase.SECURITY_ATTRIBUTES;
import com.sun.jna.platform.win32.WinBase.STARTUPINFO;
import com.sun.jna.platform.win32.WinDef.DWORD;
import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.platform.win32.WinNT.HANDLE;

/**
 * Class to manage a running process. Access to the underlying Os process is handled using JNA.
 * 
 * @author Tobias Mahlmann
 *
 */
public class Win32Process {
    private final static Logger LOGGER = Logger.getLogger( Win32Process.class.getName() );

    public static Win32Process create( String dir, String commandLine ) throws IOException {

        final PROCESS_INFORMATION processInformation = new PROCESS_INFORMATION();
        final STARTUPINFO startupInfo = new STARTUPINFO();
        final SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();

        final DWORD flags = new DWORD( 0x02000000 );//CREATE_PRESERVE_CODE_AUTHZ_LEVEL
        if (Kernel32.INSTANCE.CreateProcess( null, commandLine, sa, null, true, flags, null, dir, startupInfo, processInformation )) {
            LOGGER.info( "Spawned process id " + processInformation.dwProcessId );
            return new Win32Process( processInformation.dwProcessId.intValue() );
        }
        else {
            throw new IOException( "CreateProcess failed: " + Kernel32Util.formatMessageFromLastErrorCode( Kernel32.INSTANCE.GetLastError() ) );
        }
    }

    private final HANDLE handle;

    private final int pid;

    private Win32Process( int pid ) throws IOException {

        handle = Kernel32.INSTANCE.OpenProcess( 0x0400 | /* PROCESS_QUERY_INFORMATION */
        0x0001 /* PROCESS_TERMINATE */| WinNT.SYNCHRONIZE /* SYNCHRONIZE */, false, pid );
        if (handle == null) {
            throw new IOException( "OpenProcess of pid " + pid + " failed: " + Kernel32Util.formatMessageFromLastErrorCode( Kernel32.INSTANCE.GetLastError() ) );
        }
        this.pid = pid;
    }

    @Override
    protected void finalize() throws Throwable {
        Kernel32.INSTANCE.CloseHandle( handle );
    }

    public List<Win32Process> getChildren() throws IOException {
        LOGGER.fine( "Finding children of process id " + pid );
        final ArrayList<Win32Process> result = new ArrayList<Win32Process>();
        final WinNT.HANDLE hSnap = Kernel32.INSTANCE.CreateToolhelp32Snapshot( Tlhelp32.TH32CS_SNAPPROCESS, new DWORD( 0 ) );
        final Tlhelp32.PROCESSENTRY32.ByReference ent = new Tlhelp32.PROCESSENTRY32.ByReference();
        if (!Kernel32.INSTANCE.Process32First( hSnap, ent )) {
            return result;
        }
        do {
            if (ent.th32ParentProcessID.intValue() == pid) {
                LOGGER.fine( "Process " + ent.th32ProcessID.intValue() + " is a child" );
                result.add( new Win32Process( ent.th32ProcessID.intValue() ) );
            }
        }
        while (Kernel32.INSTANCE.Process32Next( hSnap, ent ));
        Kernel32.INSTANCE.CloseHandle( hSnap );
        return result;
    }

    /**
     * Test if our handle is in the signaled state, i.e. the process terminated.
     * Make sure this is true for all child processes.
     * @return true if the process is still running
     * @throws IOException
     */
    public boolean isNotTerminated() throws IOException {
//        final int ret = Kernel32.INSTANCE.WaitForSingleObject( handle, 0 ); // WAIT_OBJECT_0;
//
//        if (ret == WinBase.WAIT_OBJECT_0) {
//            return false;
//        }
//        else if (ret == WinError.WAIT_TIMEOUT) {
//            //Ensure that all the children are well as well
//            for (final Win32Process s : getChildren()) {
//                if (!s.isNotTerminated()) {
//                    return false;
//                }
//
//            }
//            return true;
//        }
//
//        else {
//            //Something went wrong
//            throw new IllegalStateException( Kernel32Util.formatMessageFromLastErrorCode( Kernel32.INSTANCE.GetLastError() ) );
//        }
        LOGGER.warning( "Hardcoded process respondance check to true" );
        return true;
    }

    public void terminate() throws IOException {
        for (final Win32Process s : getChildren()) {
            s.terminate();

        }
        LOGGER.info( "Terminating process " + pid );
        if (!Kernel32.INSTANCE.TerminateProcess( handle, 0 )) {

            LOGGER.warning( "Process " + pid + " did not shutdown cleanly" );
        }
    }
}
